package org.dromara.workflow.utils;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import org.dromara.common.core.domain.dto.RoleDTO;
import org.dromara.common.core.domain.dto.UserDTO;
import org.dromara.common.core.service.UserService;
import org.dromara.common.core.utils.SpringUtils;
import org.dromara.common.core.utils.StreamUtils;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.mail.utils.MailUtils;
import org.dromara.common.satoken.utils.LoginHelper;
import org.dromara.common.tenant.helper.TenantHelper;
import org.dromara.common.websocket.dto.WebSocketMessageDto;
import org.dromara.common.websocket.utils.WebSocketUtils;
import org.dromara.workflow.common.constant.FlowConstant;
import org.dromara.workflow.common.enums.MessageTypeEnum;
import org.dromara.workflow.common.enums.TaskStatusEnum;
import org.dromara.workflow.domain.ActHiTaskinst;
import org.dromara.workflow.domain.vo.MultiInstanceVo;
import org.dromara.workflow.domain.vo.ParticipantVo;
import org.dromara.workflow.flowable.cmd.UpdateHiTaskInstCmd;
import org.dromara.workflow.mapper.ActHiTaskinstMapper;
import org.flowable.bpmn.model.BpmnModel;
import org.flowable.bpmn.model.FlowNode;
import org.flowable.common.engine.api.delegate.Expression;
import org.flowable.engine.ProcessEngine;
import org.flowable.engine.history.HistoricProcessInstance;
import org.flowable.engine.impl.bpmn.behavior.ParallelMultiInstanceBehavior;
import org.flowable.engine.impl.bpmn.behavior.SequentialMultiInstanceBehavior;
import org.flowable.identitylink.api.history.HistoricIdentityLink;
import org.flowable.task.api.Task;
import org.flowable.task.api.TaskQuery;
import org.flowable.task.api.history.HistoricTaskInstance;
import org.flowable.task.service.impl.persistence.entity.TaskEntity;

import java.util.*;


/**
 * @Author 刘武贵
 * @Date 2024/8/15 22:00
 * @Description 功能描述：工作流工具
 */
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class WorkflowUtils {

    /**
     * ProcessEngine实例，用于流程引擎的操作
     * 通过SpringUtils从Spring容器中获取ProcessEngine的Bean
     * 使用@，表示这是一个常量，并且是私有的，仅在本类中使用
     */
    private static final ProcessEngine PROCESS_ENGINE = SpringUtils.getBean(ProcessEngine.class);

    /**
     * ActHiTaskinstMapper实例，用于操作历史任务实例数据
     * 通过SpringUtils从Spring容器中获取ActHiTaskinstMapper的Bean
     * 这个Mapper主要用于数据库查询，特别是与act_hi_taskinst表相关的操作
     * 同样，这是私有的静态常量，确保只在本类中被初始化和使用
     */
    private static final ActHiTaskinstMapper ACT_HI_TASKINST_MAPPER = SpringUtils.getBean(ActHiTaskinstMapper.class);

    /**
     * 创建一个新任务
     *
     * @param currentTask 参数
     * @return TaskEntity 返回新创建的任务实体，如果当前任务为空或创建失败，则返回null
     */
    public static TaskEntity createNewTask(Task currentTask) {
        TaskEntity task = null;
        // 检查当前任务是否非空
        if (ObjectUtil.isNotEmpty(currentTask)) {
            // 从流程引擎中获取新的任务实例
            task = (TaskEntity) PROCESS_ENGINE.getTaskService().newTask();
            // 复制当前任务的相关属性到新任务中
            task.setCategory(currentTask.getCategory());
            task.setDescription(currentTask.getDescription());
            task.setAssignee(currentTask.getAssignee());
            task.setName(currentTask.getName());
            task.setProcessDefinitionId(currentTask.getProcessDefinitionId());
            task.setProcessInstanceId(currentTask.getProcessInstanceId());
            task.setTaskDefinitionKey(currentTask.getTaskDefinitionKey());
            task.setPriority(currentTask.getPriority());
            // 设置任务的创建时间和租户ID
            task.setCreateTime(new Date());
            task.setTenantId(TenantHelper.getTenantId());
            // 保存任务实体
            PROCESS_ENGINE.getTaskService().saveTask(task);
        }
        // 如果任务成功创建且非空，则执行命令更新历史任务实例
        if (ObjectUtil.isNotNull(task)) {
            UpdateHiTaskInstCmd updateHiTaskInstCmd = new UpdateHiTaskInstCmd(Collections.singletonList(task.getId()), task.getProcessDefinitionId(), task.getProcessInstanceId());
            PROCESS_ENGINE.getManagementService().executeCommand(updateHiTaskInstCmd);
        }
        return task;
    }

    /**
     * 抄送任务
     *
     * @param parentTaskList 父级任务
     * @param userIds        人员id
     */
    public static void createCopyTask(List<Task> parentTaskList, List<Long> userIds) {
        // 存储新创建的抄送任务
        List<Task> list = new ArrayList<>();
        // 获取当前租户ID，用于任务的租户关联
        String tenantId = TenantHelper.getTenantId();
        // 遍历原始任务列表
        for (Task parentTask : parentTaskList) {
            // 遍历用户ID列表，为每个用户创建一个抄送任务
            for (Long userId : userIds) {
                // 创建新的任务实例
                TaskEntity newTask = (TaskEntity) PROCESS_ENGINE.getTaskService().newTask();
                // 设置新任务的父任务ID，关联到原始任务
                newTask.setParentTaskId(parentTask.getId());
                // 设置任务的执行人，根据用户ID
                newTask.setAssignee(userId.toString());
                // 设置任务名称，标记为抄送任务
                newTask.setName("【抄送】-" + parentTask.getName());
                // 以下几行复制原始任务的相关流程信息到新任务
                newTask.setProcessDefinitionId(parentTask.getProcessDefinitionId());
                newTask.setProcessInstanceId(parentTask.getProcessInstanceId());
                newTask.setTaskDefinitionKey(parentTask.getTaskDefinitionKey());
                // 设置新任务的租户ID
                newTask.setTenantId(tenantId);
                // 将新任务添加到列表中
                list.add(newTask);
            }
        }
        // 批量保存任务到数据库
        PROCESS_ENGINE.getTaskService().bulkSaveTasks(list);
        // 以下几行处理抄送任务与原始任务的关系，及任务状态记录
        if (CollUtil.isNotEmpty(list) && CollUtil.isNotEmpty(parentTaskList)) {
            String processInstanceId = parentTaskList.get(0).getProcessInstanceId();
            String processDefinitionId = parentTaskList.get(0).getProcessDefinitionId();
            List<String> taskIds = StreamUtils.toList(list, Task::getId);
            ActHiTaskinst actHiTaskinst = new ActHiTaskinst();
            actHiTaskinst.setProcDefId(processDefinitionId);
            actHiTaskinst.setProcInstId(processInstanceId);
            actHiTaskinst.setScopeType(TaskStatusEnum.COPY.getStatus());
            actHiTaskinst.setTenantId(tenantId);
            LambdaUpdateWrapper<ActHiTaskinst> updateWrapper = new LambdaUpdateWrapper<>();
            updateWrapper.in(ActHiTaskinst::getId, taskIds);
            ACT_HI_TASKINST_MAPPER.update(actHiTaskinst, updateWrapper);
            // 为每个抄送任务添加注释，标记其状态为抄送
            for (Task task : list) {
                PROCESS_ENGINE.getTaskService().addComment(task.getId(), task.getProcessInstanceId(), TaskStatusEnum.COPY.getStatus(), StrUtil.EMPTY);
            }
        }
    }

    /**
     * 获取当前任务参与者
     *
     * @param taskId 任务id
     * @param userService 用户服务，用于查询用户信息
     * @return 包含任务参与者信息的ParticipantVo对象
     */
    public static ParticipantVo getCurrentTaskParticipant(String taskId, UserService userService) {
        ParticipantVo participantVo = new ParticipantVo();
        // 查询任务的历史身份链接，用于后续判断参与者
        List<HistoricIdentityLink> linksForTask = PROCESS_ENGINE.getHistoryService().getHistoricIdentityLinksForTask(taskId);
        // 查询任务基本信息
        Task task = QueryUtils.taskQuery().taskId(taskId).singleResult();
        // 筛选带有非空组ID的历史身份链接
        if (task != null && CollUtil.isNotEmpty(linksForTask)) {
            List<HistoricIdentityLink> groupList = StreamUtils.filter(linksForTask, e -> StringUtils.isNotBlank(e.getGroupId()));
            if (CollUtil.isNotEmpty(groupList)) {
                // 提取组ID并转换为Long类型
                List<Long> groupIds = StreamUtils.toList(groupList, e -> Long.valueOf(e.getGroupId()));
                // 根据角色ID查询关联的用户ID
                List<Long> userIds = userService.selectUserIdsByRoleIds(groupIds);

                // 如果用户ID列表不为空，则设置到ParticipantVo对象中
                if (CollUtil.isNotEmpty(userIds)) {
                    participantVo.setGroupIds(groupIds);
                    List<UserDTO> userList = userService.selectListByIds(userIds);
                    // 提取用户ID和昵称，并设置到ParticipantVo对象中
                    if (CollUtil.isNotEmpty(userList)) {
                        List<Long> userIdList = StreamUtils.toList(userList, UserDTO::getUserId);
                        List<String> nickNames = StreamUtils.toList(userList, UserDTO::getNickName);
                        participantVo.setCandidate(userIdList);
                        participantVo.setCandidateName(nickNames);
                        // 判断任务是否已经被认领
                        participantVo.setClaim(!StringUtils.isBlank(task.getAssignee()));
                    }
                }
            } else {
                // 筛选任务的候选用户
                List<HistoricIdentityLink> candidateList = StreamUtils.filter(linksForTask, e -> FlowConstant.CANDIDATE.equals(e.getType()));
                List<Long> userIdList = new ArrayList<>();

                // 提取用户ID并尝试转换为Long类型
                for (HistoricIdentityLink historicIdentityLink : linksForTask) {
                    try {
                        userIdList.add(Long.valueOf(historicIdentityLink.getUserId()));
                    } catch (NumberFormatException ignored) {

                    }
                }
                List<UserDTO> userList = userService.selectListByIds(userIdList);

                // 如果用户列表不为空，则设置到ParticipantVo对象中
                if (CollUtil.isNotEmpty(userList)) {
                    List<Long> userIds = StreamUtils.toList(userList, UserDTO::getUserId);
                    List<String> nickNames = StreamUtils.toList(userList, UserDTO::getNickName);
                    participantVo.setCandidate(userIds);
                    participantVo.setCandidateName(nickNames);
                    // 判断当前任务是否具有多个办理人
                    if (CollUtil.isNotEmpty(candidateList) && candidateList.size() > 1) {
                        // 如果 assignee 存在，则设置当前任务已经被认领
                        participantVo.setClaim(StringUtils.isNotBlank(task.getAssignee()));
                    }
                }
            }
        }
        return participantVo;
    }

    /**
     * 判断当前节点是否为会签节点
     *
     * @param processDefinitionId 流程定义id
     * @param taskDefinitionKey   流程定义id
     * @return 返回会签节点信息的MultiInstanceVo对象，如果不是会签节点则返回null
     */
    public static MultiInstanceVo isMultiInstance(String processDefinitionId, String taskDefinitionKey) {
        // 根据流程定义ID获取BpmnModel对象
        BpmnModel bpmnModel = PROCESS_ENGINE.getRepositoryService().getBpmnModel(processDefinitionId);
        // 根据任务定义Key获取FlowNode对象
        FlowNode flowNode = (FlowNode) bpmnModel.getFlowElement(taskDefinitionKey);
        // 创建MultiInstanceVo对象用于封装会签节点信息
        MultiInstanceVo multiInstanceVo = new MultiInstanceVo();
        // 判断是否为并行会签节点
        if (flowNode.getBehavior() instanceof ParallelMultiInstanceBehavior behavior && behavior.getCollectionExpression() != null) {
            // 获取集合表达式
            Expression collectionExpression = behavior.getCollectionExpression();
            // 获取会签人员列表
            String assigneeList = collectionExpression.getExpressionText();
            // 获取会签人员变量名
            String assignee = behavior.getCollectionElementVariable();
            // 封装并行会签节点信息
            multiInstanceVo.setType(behavior);
            multiInstanceVo.setAssignee(assignee);
            multiInstanceVo.setAssigneeList(assigneeList);
            // 返回封装好的并行会签节点信息
            return multiInstanceVo;
            // 判断是否为串行会签节点
        } else if (flowNode.getBehavior() instanceof SequentialMultiInstanceBehavior behavior && behavior.getCollectionExpression() != null) {
            // 获取集合表达式
            Expression collectionExpression = behavior.getCollectionExpression();
            // 获取会签人员列表
            String assigneeList = collectionExpression.getExpressionText();
            // 获取会签人员变量名
            String assignee = behavior.getCollectionElementVariable();
            // 封装串行会签节点信息
            multiInstanceVo.setType(behavior);
            multiInstanceVo.setAssignee(assignee);
            multiInstanceVo.setAssigneeList(assigneeList);
            // 返回封装好的串行会签节点信息
            return multiInstanceVo;
        }
        // 如果不是会签节点则返回null
        return null;
    }

    /**
     * 获取当前流程状态
     *
     * @param taskId 任务id
     * @return 返回该任务所属的流程实例的业务状态
     * @throws 如果任务ID无效或没有找到对应的任务实例，可能返回null
     */
    public static String getBusinessStatusByTaskId(String taskId) {
        // 根据任务ID查询历史任务实例，用于获取业务状态所在的流程实例ID
        HistoricTaskInstance historicTaskInstance = QueryUtils.hisTaskInstanceQuery().taskId(taskId).singleResult();
        // 根据流程实例ID查询历史流程实例，用于直接获取业务状态
        HistoricProcessInstance historicProcessInstance = QueryUtils.hisInstanceQuery(historicTaskInstance.getProcessInstanceId()).singleResult();
        // 返回查询到的历史流程实例的业务状态
        return historicProcessInstance.getBusinessStatus();
    }

    /**
     * 获取当前流程状态
     *
     * @param businessKey 业务id
     * @return 返回业务的状态信息
     */
    public static String getBusinessStatus(String businessKey) {
        // 通过业务键查询历史流程实例，预期返回单个结果
        HistoricProcessInstance historicProcessInstance = QueryUtils.hisBusinessKeyQuery(businessKey).singleResult();
        // 返回查询到的历史流程实例的业务状态
        return historicProcessInstance.getBusinessStatus();
    }

    /**
     * 发送消息
     *
     * @param list        任务
     * @param name        流程名称
     * @param messageType 消息类型
     * @param message     消息内容，为空则发送默认配置的消息内容
     * @param userService 用户服务，用于获取用户信息和消息发送
     */
    public static void sendMessage(List<Task> list, String name, List<String> messageType, String message, UserService userService) {
        // 用于存储需要发送消息的用户ID，以避免重复发送
        Set<Long> userIds = new HashSet<>();

        // 如果未提供自定义消息，则构造默认消息内容
        if (StringUtils.isBlank(message)) {
            message = "有新的【" + name + "】单据已经提交至您的待办，请您及时处理。";
        }

        // 遍历待办任务列表，获取当前任务的参与者信息
        for (Task t : list) {
            ParticipantVo taskParticipant = WorkflowUtils.getCurrentTaskParticipant(t.getId(), userService);

            // 如果当前任务参与者关联了用户组，则获取组内用户ID列表
            if (CollUtil.isNotEmpty(taskParticipant.getGroupIds())) {
                List<Long> userIdList = userService.selectUserIdsByRoleIds(taskParticipant.getGroupIds());
                if (CollUtil.isNotEmpty(userIdList)) {
                    userIds.addAll(userIdList);
                }
            }

            // 如果当前任务参与者有候补人员，则添加候补人员的用户ID
            List<Long> candidate = taskParticipant.getCandidate();
            if (CollUtil.isNotEmpty(candidate)) {
                userIds.addAll(candidate);
            }
        }

        // 如果存在需要通知的用户，则根据消息类型发送消息
        if (CollUtil.isNotEmpty(userIds)) {
            // 获取所有需要通知的用户信息
            List<UserDTO> userList = userService.selectListByIds(new ArrayList<>(userIds));
            // 遍历每种消息类型，发送相应类型的消息
            for (String code : messageType) {
                MessageTypeEnum messageTypeEnum = MessageTypeEnum.getByCode(code);
                if (ObjectUtil.isNotEmpty(messageTypeEnum)) {
                    switch (messageTypeEnum) {
                        case SYSTEM_MESSAGE:
                            // 构造系统消息并发送
                            WebSocketMessageDto dto = new WebSocketMessageDto();
                            dto.setSessionKeys(new ArrayList<>(userIds));
                            dto.setMessage(message);
                            WebSocketUtils.publishMessage(dto);
                            break;
                        case EMAIL_MESSAGE:
                            // 发送邮件通知
                            MailUtils.sendText(StreamUtils.join(userList, UserDTO::getEmail), "单据审批提醒", message);
                            break;
                        case SMS_MESSAGE:
                            //todo 短信发送
                            break;
                    }
                }
            }
        }
    }

    /**
     * 根据任务id查询 当前用户的任务，检查 当前人员 是否是该 taskId 的办理人
     *
     * @param taskId 任务id
     * @return 结果
     */
    public static Task getTaskByCurrentUser(String taskId) {
        // 初始化任务查询条件，设置任务ID和当前用户为任务的候选执行者或已分配人员
        TaskQuery taskQuery = QueryUtils.taskQuery();
        taskQuery.taskId(taskId).taskCandidateOrAssigned(String.valueOf(LoginHelper.getUserId()));

        // 获取当前用户的所有角色
        List<RoleDTO> roles = LoginHelper.getLoginUser().getRoles();
        // 如果角色列表不为空，则进一步筛选任务候选组
        if (CollUtil.isNotEmpty(roles)) {
            // 将角色ID列表转换为字符串ID的列表
            List<String> groupIds = StreamUtils.toList(roles, e -> String.valueOf(e.getRoleId()));
            // 设置查询条件，只考虑属于这些角色组的任务
            taskQuery.taskCandidateGroupIn(groupIds);
        }
        // 执行查询并返回结果
        return taskQuery.singleResult();
    }
}
